#include "SampleSpace.h"
#include "Distance.h"
#include "Toolkit.h"

#include <cstring>
#include <fstream>
#include <sstream>
#include <iostream>
#include <cmath>
#include <cassert>
#include <cfloat>

using namespace std;


class _Sample : public Sample {
public:
    _Sample(int dim) : Sample(dim) {
    }

    _Sample(int dim, int _class) : Sample(dim, _class) {
    }

    _Sample(int dim, int _class, float *data) : Sample(dim, _class, data) {
    };

    _Sample(const Sample &sample) : Sample(sample) {
    };

};

class _Euclidean : public Distance {
public:
    float calcDistance(const Sample &s1, const Sample &s2);

    bool calcDistance2(const Sample &s1, const Sample &s2, float &out, float threshold);

    _Euclidean(int dim) : Distance(dim) {
        m_type = EUCLIDEAN;
    }

};

SampleSpace::SampleSpace(int dim, Distance::DistanceType distType) {
    m_dim = dim;

    switch (distType) {
        case Distance::EUCLIDEAN:
            m_distance = new _Euclidean(dim);
            break;

        default:
            assert(false);
            break;
    }
}

SampleSpace::~SampleSpace() {
    delete m_distance;
}

Sample * SampleSpace::newSample() {
    return new _Sample(m_dim);
}

void SampleSpace::deleteSample(Sample **sample) {
    delete (_Sample *)*sample;
    *sample = NULL;
}

void SampleSpace::newSampleArray(vector<Sample *> &sampleList, int len) {
    for(int i = 0; i < len; i++) {
        sampleList.push_back(new _Sample(m_dim));
    }
}

void SampleSpace::deleteSampleArray(vector<Sample *> &sampleList) {
    int size = sampleList.size();
    for(int i = 0; i < size; i++) {
        delete (_Sample *)sampleList.at(i);
    }

    sampleList.clear();
}

void SampleSpace::store2Stream(ofstream &fout) {
    fout << m_dim << " " << m_distance->getDistanceType() << endl;
}

SampleSpace * SampleSpace::loadFromStream(ifstream &fin) {
    int dim;
    int type;
    
    fin >> dim >> type;
    return new SampleSpace(dim, (Distance::DistanceType)type);
}

float _Euclidean::calcDistance(const Sample &s1, const Sample &s2) {
    assert(s1.getDimension() == s2.getDimension());

    const float *f1 = s1.getFeature();
    const float *f2 = s2.getFeature();

    float temp;
    float dist = 0;
    int dim = s1.getDimension();
    for (int i = 0; i < dim; i++) {
        temp = f1[i] - f2[i];
        dist += temp*temp;
    }

    dist = sqrt(dist);

    return dist;
}

bool _Euclidean::calcDistance2(const Sample &s1, const Sample &s2, float &out, float threshold) {
    assert(s1.getDimension() == s2.getDimension());

    float *f1 = s1.getFeature();
    float *f2 = s2.getFeature();

    float temp;
    out = 0;
    int dim = s1.getDimension();
    for (int i = 0; i < dim; i++) {
        temp = f1[i] - f2[i];
        out += temp*temp;

        if (out >= threshold) {
            return false;
        }
    }

    return true;
}

void SampleSpace::storeSample2Stream(ofstream &fout, vector<Sample *> &sampleList) {
    int size = sampleList.size();
    
    fout << size << " " << m_dim << endl;
    float *feature = NULL;
    for(int i = 0; i < size; i++) {        
        fout << sampleList.at(i)->getClass() << ":";
        
        feature = sampleList.at(i)->getFeature();
        for(int j = 0; j < m_dim; j++) {
            fout << " " << feature[j];
        }
        fout << endl;
    }
}

bool SampleSpace::loadSampleFromStream(ifstream &fin, vector<Sample *> &sampleList) {
    int dim, size;
    fin >> size >> dim;
    if(size < 0 || dim != m_dim) {
        return false;
    }

    stringstream ss;

    string line;
    Sample *sample = NULL;
    float *data = new float[m_dim];
    int _class;

    getline(fin, line); // read the end of the last line
    for(int i = 0; i < size; i++) {
        ss.clear();
        
        getline(fin, line);
        line.replace(line.find_first_of(":"), 1, " ");
        
        ss << line;

        ss >> _class;
        for (int i = 0; i < m_dim; i++) {
            ss >> data[i];
        }

        sample = new _Sample(m_dim, _class, data);
        sampleList.push_back(sample);
    }

    delete[] data;

    return true;
}

void SampleSpace::selfNormalize(vector<Sample *> &sampleList) {
	float *min = new float[m_dim];
	float *max = new float[m_dim];
	for(int i = 0; i < m_dim; ++i) {
		min[i] = FLT_MAX;
	}
	memset(max, 0, m_dim*sizeof(float));

	float *feature = NULL;
	int size = sampleList.size();
	for(int i = 0; i < size; ++i) {
		feature = sampleList.at(i)->getFeature();

		for(int j = 0; j < m_dim; ++j) {
			if(feature[j] < min[j]) {
				min[j] = feature[j];
			}

			if(feature[j] > max[j]) {
				max[j] = feature[j];
			}
		}
	}

	for(int i = 0; i < size; ++i) {
		feature = sampleList.at(i)->getFeature();

		for(int j = 0; j < m_dim; ++j) {
			feature[j] = (feature[j] - min[j]) / (max[j] - min[j]);
		}
	}

	delete[] min;
	delete[] max;
}

void SampleSpace::sampling(vector<Sample *> &sampleList, vector<Sample *> &subset, float factor) {
	int size = sampleList.size();
	int sampleCount = (int) (factor * size);

	assert(sampleCount > 0);

	int *numbers = new int[sampleCount];

	UniformRandom ur(0, size, true, false);
	int temp;
	for(int i = 0; i < sampleCount; ++i) {
		temp = numbers[i] = (int) ur.generate();
		if(temp >= (int)sampleList.size()) {
			--i;
			continue;
		}

		subset.push_back(sampleList.at(temp));

		Toolkit::swap(sampleList, temp, sampleList.size() - 1);
		sampleList.pop_back();
	}

	delete[] numbers;
}
